<?php
/**
 * Files management - system module.
 *
 * @author Martin "DevelX" Jurča
 */
class Files extends Module {
    protected $name = 'Files';
    protected $administrable = true;
    protected $version = '2009-09-09';
    protected $system = true;
    protected $details = array('dependencies' => array(),
            'observes' => array('Files'));

    const FILES_TABLE = 'files';
    const CATEGORIES_TABLE = 'fileCategories';
    private $filesTable = array('id' => 'iPN+', 'name' => 'sN', 'category' =>
            'iN', 'mime' => 's', 'localName' => 's');
    private $categoriesTable = array('id' => 'iPN+', 'name' => 'sN',
            'parent' => 'iN');
    private $fileRecord = null;
    private $categoryRecord = null;

    public function  __construct(DBAPI $dbapi, RecordList $recordList) {
        parent::__construct($dbapi, $recordList);
        $this->fileRecord = array('table' => self::FILES_TABLE, 'properties' =>
                $this->filesTable, 'data' => array());
        $this->categoryRecord = array('table' => self::CATEGORIES_TABLE,
                'properties' => $this->categoriesTable, 'data' => array());
        $this->categoryRecord['properties']['files'] = array(
            'linkBy' => 'category', 'linkTo' => 'id', 'linkOp' => '='
        );
        $this->categoryRecord['properties']['files']['record'] = array(
            'table' => self::FILES_TABLE, 'properties' => array('id' => 'iP',
            'name' => 's', 'category' => 'i', 'mime' => 's', 'localName' =>
            's'), 'data' => array()
        );
        $this->categoryRecord['properties']['categories'] = array(
            'linkBy' => 'parent', 'linkTo' => 'id', 'linkOp' => '='
        );
        $this->categoryRecord['properties']['categories']['record'] = array(
            'table' => self::CATEGORIES_TABLE, 'properties' => array('id' =>
            'iP', 'name' => 's', 'parent' => 'i'), 'data' => array()
        );
    }

    public function install() {
        FTP::chmod($this->getDir('/uploaded', true));
        $this->dbdriver->createTable(self::CATEGORIES_TABLE,
                $this->categoriesTable);
        $this->dbdriver->createTable(self::FILES_TABLE,
                $this->filesTable);
        Modules::getModule('Users')->registerRights($this->name,
                $this->getRights());
        return true;
    }

    public function uninstall() {
        $this->dbdriver->dropTable(self::CATEGORIES_TABLE);
        $this->dbdriver->dropTable(self::FILES_TABLE);
        return true;
    }

    public function display($param = null) {
        if (Kernel::getMode() == Kernel::ADMIN) {
            $this->displayAdmin($param);
        }
    }

    public function &getCategory($id, $tree = false) {
        $category = &$this->dbapi->load($this->categoryRecord, '`id` = ' .
                (int) $id);
        if (count($category) == 0) {
            $category = false;
        }
        if ($category === false) {
            return $category;
        }
        $category = $category[0];
        if ($tree) {
            foreach ($category['data']['categories'] as &$categ) {
                $categ = &$this->getCategory($categ['id'], true);
            }
        }
        return $category;
    }

    public function saveCategory(&$category) {
        if (!isset($category['data']['id']) ||
                !is_numeric($category['data']['id'])) {
            if (!$this->hasRights('create categories')) {
                return false;
            }
            return $this->dbapi->save($category);
        } else {
            if (!$this->hasRights('edit categories')) {
                return false;
            }
            return $this->dbapi->update($category);
        }
    }

    public function &getCategories($rootId = 0) {
        if ($rootId != 0) {
            $root = &$this->getCategory($rootId);
            if ($root === false) {
                return $root;
            }
            $categories = array();
            foreach ($root['data']['categories'] as $category) {
                $categories[] = &$this->getCategory($category['data']['id']);
            }
        } else {
            $categories = &$this->dbapi->load($this->categoryRecord,
                    '`parent` = 0');
        }
        $result = $categories;
        foreach ($categories as $category) {
            $subcategories = &$this->getCategories($category['data']['id']);
            $result = array_merge($result, $subcategories);
        }
        return $result;
    }
    
    public function &getCategoryContent($rootId = 0) {
        $cols = array('id', 'name');
        $content = &$this->dbdriver->select(self::CATEGORIES_TABLE, $cols,
                '`parent` = ' . (int) $rootId);
        return $content;
    }

    public function deleteCategory($id) {
        if (!Modules::getModule('Users')->checkRights('Files',
                'delete categories')) {
            return;
        }
        $category = $this->categoryRecord;
        $category['data']['id'] = (int) $id;
        $this->dbapi->refresh($category);
        foreach ($category['data']['categories'] as $subCategory) {
            $this->deleteCategory($subCategory['data']['id']);
        }
        foreach ($category['data']['files'] as $file) {
            $this->deleteFile($file['data']['id']);
        }
        $this->dbapi->delete($category);
    }

    public function &getFile($id) {
        $file = $this->dbapi->load($this->fileRecord,  '`id` = ' . $id);
        if (isset($file[0])) {
            $file = $file[0];
        }
        return $file;
    }

    public function &getFiles($categoryId) {
        $category = &$this->getCategory($id);
        if ($category === false) {
            return $category;
        }
        $files = array();
        foreach ($category['data']['file'] as $file) {
            $files[] = &$this->getFile($file['data']['id']);
        }
        return $files;
    }

    public function saveFile(&$file) {
        if (!isset($file['data']['id']) || !is_numeric($file['data']['id'])) {
            if (!$this->hasRights('create files')) {
                return false;
            }
            return $this->dbapi->save($file);
        } else {
            if (!$this->hasRights('edit files')) {
                return false;
            }
            return $this->dbapi->update($file);
        }
    }

    public function deleteFile($id) {
        if (!Modules::getModule('Users')->checkRights('Files',
                'delete files')) {
            return false;
        }
        $file = $this->fileRecord;
        $file['data']['id'] = (int) $id;
        $this->dbapi->refresh($file);
        FTP::deleteFile($this->getDir("/uploaded/{$file['data']['localName']}",
                true));
        return $this->dbapi->delete($file);
    }

    public function createFile($category, $name = '', $ext = '') {
        $cols = array('id');
        $check = &$this->dbdriver->select(self::CATEGORIES_TABLE, $cols,
                '`id` = ' . (int) $category);
        if (!isset($check[0])) {
            return false;
        }
        $file = $this->fileRecord;
        $file['data'] = array('category' => (int) $category, 'name' => $name);
        $this->dbapi->save($file);
        FTP::createFile($this->getDir('/uploaded/' . $file['data']['id'] .
                ($ext == '' ? '' : ".$ext"), true));
        $file['data']['localName'] = $file['data']['id'] . ($ext == '' ? '' :
                ".$ext");
        $this->dbapi->update($file);
        return $file;
    }

    public function &uploadFiles(&$files, $categoryId) {
        if (!$this->hasRights('create files')) {
            $categoryId = false;
            return $categoryId;
        }
        $category = &$this->getCategory($categoryId);
        if (($category === false) || !is_array($files)) {
            $category = false;
            return $category;
        }
        $fileRecords = array();
        foreach ($files as $file) {
            if (isset($file['error']) && is_array($file['error'])) {
                foreach ($file['error'] as $key => $err) {
                    if ($err == UPLOAD_ERR_OK) {
                        $fileDetails = array('name' => $file['name'][$key],
                                'type' => $file['type'][$key], 'error' => $err,
                                'tmp_name' => $file['tmp_name'][$key],
                                'size' => $file['size'][$key]);
                        $fileRecord = &$this->uploadFile($fileDetails,
                                $categoryId);
                        if ($fileRecord !== false) {
                            $fileRecords[] = $fileRecord;
                        }
                    }
                }
            } elseif (isset($file['error'])
                    && ($file['error'] == UPLOAD_ERR_OK)) {
                $fileRecord = &$this->uploadFile($file, $categoryId);
                if ($fileRecord !== false) {
                    $fileRecords[] = $fileRecord;
                }
            }
        }
        return $fileRecords;
    }

    public function &mirrorFiles(&$files, $categoryId) {
        if (!$this->hasRights('create files')) {
            $categoryId = false;
            return $categoryId;
        }
        if (!is_array($files)) {
            $categoryId = false;
            return $categoryId;
        }
        $category = &$this->getCategory($categoryId);
        if ($category === false) {
            return $category;
        }
        $mirroredFiles = array();
        foreach ($files as $file) {
            if ($file != '') {
                $fileRecord = &$this->mirrorFile($file, $categoryId);
                if ($fileRecord !== false) {
                    $mirroredFiles[] = $fileRecord;
                }
            }
        }
        return $mirroredFiles;
    }

    public function &onAddCategory(&$details, $source) {
        Kernel::setRedirect($this->getLink());
        $result = null;
        if (!Modules::getModule('Users')->checkRights('Files',
                'create categories')) {
            Modules::getModule('View')->addMessage(
                    $this->lang('insufficentRights'), View::MSG_ERROR);
            return $result;
        }
        $category = $this->categoryRecord;
        $category['data'] = array('name' => $details['name'], 'parent' =>
                $details['parent']);
        $this->dbapi->save($category);
        return $result;
    }

    public function &onUpload(&$details, $source) {
        Kernel::setRedirect($this->getLink());
        $result = null;
        if (!Modules::getModule('Users')->checkRights('Files',
                'create files')) {
            Modules::getModule('View')->addMessage(
                    $this->lang('insufficentRights'), View::MSG_ERROR);
            return $result;
        }
        $this->uploadFiles($_FILES, $details['category']);
        return $result;
    }

    public function &onMirror(&$details, $source) {
        Kernel::setRedirect($this->getLink());
        $result = null;
        if (!Modules::getModule('Users')->checkRights('Files',
                'create files')) {
            Modules::getModule('View')->addMessage(
                    $this->lang('insufficentRights'), View::MSG_ERROR);
            return $result;
        }
        $this->mirrorFiles($details['urls'], $details['category']);
        return $result;
    }

    public function &onEditCategory(&$details, $source) {
        Kernel::setRedirect($this->getLink());
        $result = null;
        if (!Modules::getModule('Users')->checkRights('Files',
                'edit categories')) {
            Modules::getModule('View')->addMessage(
                    $this->lang('insufficentRights'), View::MSG_ERROR);
            return $result;
        }
        $category = $this->categoryRecord;
        $category['data']['id'] = (int) $details['id'];
        $this->dbapi->refresh($category);
        $category['data']['name'] = $details['name'];
        $category['data']['parent'] = $details['parent'];
        $this->dbapi->update($category);
        return $result;
    }

    public function &onDeleteFile(&$details, $source) {
        Kernel::setRedirect($this->getLink());
        $result = null;
        if (!Modules::getModule('Users')->checkRights('Files',
                'delete files')) {
            Modules::getModule('View')->addMessage(
                    $this->lang('insufficentRights'), View::MSG_ERROR);
            return $result;
        }
        $this->deleteFile((int) substr($details, 0, -1));
        return $result;
    }

    public function &onDeleteCategory(&$details, $source) {
        Kernel::setRedirect($this->getLink());
        $result = null;
        if (!Modules::getModule('Users')->checkRights('Files',
                'delete categories')) {
            Modules::getModule('View')->addMessage(
                    $this->lang('insufficentRights'), View::MSG_ERROR);
            return $result;
        }
        $this->deleteCategory((int) substr($details, 0, -1));
        return $result;
    }

    public function &onEditFile(&$details, $source) {
        Kernel::setRedirect($this->getLink());
        $result = null;
        if (!Modules::getModule('Users')->checkRights('Files',
                'edit files')) {
            Modules::getModule('View')->addMessage(
                    $this->lang('insufficentRights'), View::MSG_ERROR);
            return $result;
        }
        $file = $this->fileRecord;
        $file['data']['id'] = (int) $details['id'];
        $this->dbapi->refresh($file);
        $file['data']['name'] = $details['name'];
        $file['data']['category'] = $details['category'];
        $file['data']['mime'] = $details['mime'];
        $this->dbapi->update($file);
        return $result;
    }

    /**
     * Displays module administration interface.
     *
     * @param mixed $param Display parameter.
     */
    private function displayAdmin($param) {
        if (!Modules::getModule('Users')->checkRights('Files', 'view admin')) {
            return;
        }
        if ($param == 'submenu') {
            $this->displayAdminSubmenu();
        } else {
            $details = Modules::getModule('View')->getUrl();
            if (count($details) == 2) {
                switch ($details[1]) {
                    case 'add-category':
                        $this->displayAdminAddCategory();
                        break;
                    case 'upload-files':
                        $this->displayAdminUploadFiles();
                        break;
                    case 'mirror-files':
                        $this->displayAdminMirrorFiles();
                        break;
                    default:
                        $this->displayAdminBrowseFiles();
                        break;
                }
            } elseif (count($details) == 1) {
                $this->displayAdminBrowseFiles();
            } elseif (count($details) == 3) {
                switch ($details[1]) {
                    case 'edit-category':
                        $this->displayAdminEditCategory();
                        break;
                    case 'edit-file':
                        $this->displayAdminEditFile();
                        break;
                }
            }
        }
    }

    /**
     * Displays administration interface for editing file details.
     *
     */
    private function displayAdminEditFile() {
        if (!Modules::getModule('Users')->checkRights('Files',
                'edit files')) {
            return;
        }
        $details = Modules::getModule('View')->getUrl(1);
        $file = $this->fileRecord;
        $file['data']['id'] = (int) $details[0];
        $this->dbapi->refresh($file);
        $file = $file['data'];
        $categories = &$this->dbapi->load($this->categoryRecord);
        foreach ($categories as &$category) {
            $category = $category['data'];
        }
        array('id' => 'iPN+', 'name' => 'sN', 'category' =>
            'iN', 'mime' => 's', 'localName' => 's');
        $params = array('id' => $file['id'], 'editFile' =>
                $this->lang('editFile'), 'fileName' => $this->lang('fileName'),
                'name' => $file['name'], 'categoryName' =>
                $this->lang('categoryName'), 'mimeType' =>
                $this->lang('mimeType'), 'mime' => $file['mime'], 'save' =>
                $this->lang('save'), 'categories' => $categories, 'category' =>
                $file['category'], 'formStart' => $this->getForm('editFile',
                true, 'Files'));
        $this->template('editfile', $params);
    }

    /**
     * Displays administration interface for editing categories.
     *
     */
    private function displayAdminEditCategory() {
        if (!Modules::getModule('Users')->checkRights('Files',
                'edit categories')) {
            return;
        }
        $details = Modules::getModule('View')->getUrl(1);
        $category = &$this->dbapi->load($this->categoryRecord, '`id` = ' .
                (int) $details[0]);
        $categId = $category[0]['data']['id'];
        $parentId = $category[0]['data']['parent'];
        $category = $category[0]['data']['name'];
        $categories = &$this->dbapi->load($this->categoryRecord);
        foreach ($categories as &$categ) {
            $categ = $categ['data'];
        }
        $params = array('category' => $category, 'categories' =>
                $categories, 'categoryId' => $categId, 'editCategory' =>
                $this->lang('editCategory'), 'name' =>
                $this->lang('categoryName'), 'parent' =>
                $this->lang('parentCategory'), 'formStart' =>
                $this->getForm('editCategory', true, 'Files'), 'parentId' =>
                $parentId, 'save' => $this->lang('save'));
        $this->template('editcategory', $params);
    }

    /**
     * Displays administration interface for files and file categories.
     *
     */
    private function displayAdminBrowseFiles() {
        if (!Modules::getModule('Users')->checkRights('Files', 'view files')) {
            return;
        }
        $details = Modules::getModule('View')->getUrl(1);
        if (is_numeric($details[0])) {
            $category = &$this->dbapi->load($this->categoryRecord, '`id` = ' .
                    (int) $details[0]);
            $categories = $category[0]['data']['categories'];
            $files = $category[0]['data']['files'];
            $parent = (int) $category[0]['data']['parent'];
            $category = $category[0]['data']['name'];
            $parentLink = $this->getLink($parent === 0 ? '' : "$parent/");
        } else {
            $categories = &$this->dbapi->load($this->categoryRecord,
                    '`parent` = 0');
            $files = &$this->dbapi->load($this->fileRecord, '`category` = 0');
            $category = '/';
            $parent = false;
            $parentLink = '#';
        }
        foreach ($categories as &$categ) {
            $categ = $categ['data'];
            $categ['view'] = $this->getLink($categ['id'] . '/');
            $categ['edit'] = $this->getLink("edit-category/{$categ['id']}/");
            $categ['delete'] = $this->getLink("{$categ['id']}/",
                    'deleteCategory');
        }
        foreach ($files as &$file) {
            $file = $file['data'];
            $file['edit'] = $this->getLink("edit-file/{$file['id']}/");
            $file['delete'] = $this->getLink($file['id'] . '/', 'deleteFile');
        }
        $params = array('categories' => $categories, 'files' => $files,
                'category' => $category, 'parent' => $parent, 'view' =>
                $this->lang('view'), 'delete' => $this->lang('delete'),
                'parentLink' => $parentLink, 'edit' => $this->lang('edit'));
        $this->template('browse', $params);
    }

    /**
     * Displays administration interface for file mirroring.
     *
     */
    private function displayAdminMirrorFiles() {
        if (!Modules::getModule('Users')->checkRights('Files',
                'create files')) {
            return;
        }
        $categories = &$this->dbapi->load($this->categoryRecord);
        foreach ($categories as &$category) {
            $category = $category['data'];
        }
        $params = array('categories' => $categories, 'mirrorFiles' =>
                $this->lang('mirrorFiles'), 'formStart' =>
                $this->getForm('mirror', true, 'Files'), 'category' =>
                $this->lang('category'), 'mirror' => $this->lang('mirror'));
        $this->template('mirror', $params);
    }

    /**
     * Displays administration interface for file upload.
     *
     */
    private function displayAdminUploadFiles() {
        if (!Modules::getModule('Users')->checkRights('Files',
                'create files')) {
            return;
        }
        $categories = &$this->dbapi->load($this->categoryRecord);
        foreach ($categories as &$category) {
            $category = $category['data'];
        }
        $params = array('categories' => $categories, 'uploadFiles' =>
                $this->lang('uploadFiles'), 'upload' => $this->lang('upload'),
                'formStart' => $this->getForm('upload', true, 'Files'),
                'category' => $this->lang('category'));
        $this->template('upload', $params);
    }

    /**
     * Displays admin interface for adding new categories.
     *
     */
    private function displayAdminAddCategory() {
        if (!Modules::getModule('Users')->checkRights('Files',
                'create categories')) {
            return;
        }
        $params = array('addCategory' => $this->lang('addCategory'), 'name' =>
                $this->lang('categoryName'), 'parent' =>
                $this->lang('parentCategory'), 'create' =>
                $this->lang('create'), 'formStart' =>
                $this->getForm('addCategory', true, 'Files'));
        $categories = &$this->dbapi->load($this->categoryRecord);
        foreach ($categories as &$category) {
            $category = $category['data'];
        }
        $params['categories'] = $categories;
        $this->template('addcategory', $params);
    }

    /**
     * Displays submenu.
     *
     */
    private function displayAdminSubmenu() {
        $param = array('menu' => array(array('href' => $this->getLink(),
                'title' => $this->lang('categListTitle'), 'label' =>
                $this->lang('categList')), array('href' =>
                $this->getLink('add-category/'), 'title' =>
                $this->lang('addCategoryTitle'), 'label' =>
                $this->lang('addCategory')), array('href' =>
                $this->getLink('upload-files/'), 'title' =>
                $this->lang('uploadFilesTitle'), 'label' =>
                $this->lang('uploadFiles')), array('href' =>
                $this->getLink('mirror-files/'), 'title' =>
                $this->lang('mirrorFilesTitle'), 'label' =>
                $this->lang('mirrorFiles'))));
        $this->template('submenu', $param);
    }

    /**
     * Creates mirror of file from remote server.
     *
     * @param String $file URL to file
     * @param int $categoryId Category ID number where the file should be
     *        saved.
     * @return Array Filerecord of mirrored file.
     */
    private function &mirrorFile($file, $categoryId) {
        $fileRecord = $this->fileRecord;
        $parts = pathinfo($file);
        $fileRecord['data'] = array('name' => $parts['basename'], 'category' =>
                $categoryId);
        $this->dbapi->save($fileRecord);
        $fileRecord['data']['localName'] = $fileRecord['data']['id'] . '.' .
                Kernel::createUrl($parts['extension']);
        $localURL = $this->getDir('/uploaded/' .
                $fileRecord['data']['localName'], true);
        if (file_exists(Config::ROOT . $localURL)) {
            FTP::chmod($localURL);
        } else {
            FTP::createFile($localURL);
        }
        $remote = @fopen($file, 'rb');
        if (!is_resource($remote)) {
            $this->dbapi->delete($fileRecord);
            $fileRecord = false;
            return $fileRecord;
        }
        $local = @fopen(Config::ROOT . $localURL, 'wb');
        if (!is_resource($local)) {
            $this->dbapi->delete($fileRecord);
            $fileRecord = false;
            return $fileRecord;
        }
        while (!@feof($remote)) {
            $buffer = @fread($remote, 4096);
            @fwrite($local, $buffer);
        }
        @fclose($remote);
        @fclose($local);
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $fileRecord['data']['mime'] = finfo_file($finfo, Config::ROOT .
                $localURL);
        $this->dbapi->update($fileRecord);
        return $fileRecord;
    }

    /**
     * Process uploaded file, store it and create record of the file.
     *
     * @param Array $file File details extracted from $_FILES global array.
     * @param int $categoryId Category ID number where the file should be
     *        uploaded.
     * @return Array File record.
     */
    private function &uploadFile($file, $categoryId) {
        $fileRecord = $this->fileRecord;
        if (function_exists('finfo_open')) {
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $mime = finfo_file($finfo, $file['tmp_name']);
        } elseif (function_exists('mime_content_type')) {
            $mime = mime_content_type($file['tmp_name']);
        } else {
            $mime = $file['mime'];
        }
        $fileRecord['data'] = array('name' => $file['name'],
                        'category' => $categoryId, 'mime' => $mime);
        $this->dbapi->save($fileRecord);
        $parts = pathinfo($fileRecord['data']['name']);
        $fileRecord['data']['localName'] = $fileRecord['data']['id'] . '.' .
                Kernel::createUrl($parts['extension']);
        $this->dbapi->update($fileRecord);
        if (!file_exists($this->getDir('/uploaded/' .
                    $fileRecord['data']['localName']))) {
            FTP::createFile($this->getDir('/uploaded/' .
                    $fileRecord['data']['localName'], true));
        } else {
            FTP::chmod($this->getDir('/uploaded/' .
                    $fileRecord['data']['localName'], true));
        }
        if (!move_uploaded_file($file['tmp_name'],
                $this->getDir('/uploaded/' .
                    $fileRecord['data']['localName']))) {
            $this->dbapi->delete($fileRecord);
            $fileRecord = false;
        }
        return $fileRecord;
    }
}
?>
